﻿using System;
using Grimgine.Math;

namespace Grimgine.Graphics
{
    /// <summary>
    /// Defines a color using red, green, blue and alpha components. The range
    /// for each component is either [0, 255] or [0f, 1f].
    /// </summary>
    public struct Color : IEquatable<Color>
    {
        #region Fields

        /// <summary>
        /// Unsigned integer representing a color in the format 0xAABBGGRR
        /// </summary>
        public uint PackedValue;
        
        #endregion

        #region Properties

        /// <summary>
        /// Red component of the Color
        /// </summary>
        public byte R
        {
            get { return (byte)PackedValue; }
            set { PackedValue = (PackedValue & 0xFFFFFF00) | (uint) value; }
        }

        /// <summary>
        /// Green component of the Color
        /// </summary>
        public byte G
        {
            get { return (byte) (PackedValue >> 8); }
            set { PackedValue = (PackedValue & 0xFFFF00FF) | (uint) (value << 8); }
        }

        /// <summary>
        /// Blue component of the Color
        /// </summary>
        public byte B
        {
            get { return (byte) (PackedValue >> 16); }
            set { PackedValue = (PackedValue & 0xFF00FFFF) | (uint) (value << 16); }
        }

        /// <summary>
        /// Alpha component of the Color
        /// </summary>
        public byte A
        {
            get { return (byte) (PackedValue >> 24); }
            set { PackedValue = (PackedValue & 0x00FFFFFF) | (uint) (value << 24); }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        public Color(float r, float g, float b)
        {
            PackedValue = PackColor(r, g, b, 255f);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        public Color(int r, int g, int b)
        {
            if (((r | g | b) & 0xFFFFFF00) != 0)
            {
                r = MathHelper.ClampToByte(r);
                g = MathHelper.ClampToByte(g);
                b = MathHelper.ClampToByte(b);
            }
            PackedValue = ((uint)r) | ((uint)(g << 8)) | ((uint)(b << 16)) | 0xFF000000;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public Color(float r, float g, float b, float a)
        {
            PackedValue = PackColor(r, g, b, a);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <param name="a"></param>
        public Color(int r, int g, int b, int a)
        {
            if (((r | g | b) & 0xFFFFFF00) != 0)
            {
                r = MathHelper.ClampToByte(r);
                g = MathHelper.ClampToByte(g);
                b = MathHelper.ClampToByte(b);
                a = MathHelper.ClampToByte(a);
            }
            PackedValue = ((uint)r) | ((uint)(g << 8)) | ((uint)(b << 16)) | ((uint)(a << 24));
        }

        #endregion

        #region Comparison

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(Color a, Color b)
        {
            return ( a.R == b.R && a.G == b.G && a.B == b.B && a.A == b.A);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(Color a, Color b)
        {
            return !(a == b);
        }

        #endregion

        #region Arithmetic
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="scale"></param>
        /// <returns></returns>
        public static Color operator *(Color value, float scale)
        {
            return
            (
                new Color
                (
                    value.R * scale,
                    value.G * scale,
                    value.B * scale,
                    value.A * scale
                )
            );
        }

        #endregion

        #region IEquatable implentaion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return obj is Color && this == (Color)obj;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(Color other)
        {
            return (this == other);
        }

        #endregion

        #region Hash Code

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return (int)((R + G + B + A) % Int32.MaxValue);
        }

        #endregion

        /// <summary>
        /// Packs float color values ranging from [0f, 1f] into a unsigned
        /// integer converting it to the range of [0, 255], in the format 
        /// 0xAABBGGRR.
        /// </summary>
        private static uint PackColor(float r, float g, float b, float a)
        {
            uint n  = (uint)MathHelper.ClampAndRound(r * 255f, 0f, 255f);
            uint n1 = ((uint)MathHelper.ClampAndRound(g * 255f, 0f, 255f)) << 8;
            uint n2 = ((uint)MathHelper.ClampAndRound(b * 255f, 0f, 255f)) << 16;
            uint n3 = ((uint)MathHelper.ClampAndRound(a * 255f, 0f, 255f)) << 24;

            return n | n1 | n2 | n3;
        }
    }
}
